﻿//Copyright (c) Microsoft Corporation.  All rights reserved.

using BookSleeve;
using Microsoft.Ted.Wacel.Base;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Ted.Wacel.Redis
{
    public class RedisCacheProvider : Base.IDataProvider, IDisposable
    {
        private RedisConnection mConnection;
        private RedisConfiguration mConfiguration;
        private string mName;

        public RedisCacheProvider(RedisConnection connection, string name)
        {
            mConnection = connection;
            mConnection.Wait(mConnection.Open());
            mName = name;
        }
        public void Put(string key, object value, string partition = null)
        {
            if (value == null)
                throw new ArgumentNullException("Value can't be null.");
            else if (value is byte[])
                mConnection.Wait(mConnection.Strings.Set(0, mName + ":" + key, (byte[])value));
            else
                mConnection.Wait(mConnection.Strings.Set(0, mName + ":" + key, JsonSerializer.Serialize(value.GetType(), value)));
        }

        public object Get(string key, string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            var value = mConnection.Strings.Get(0, mName + ":" + key);
            var ret =  mConnection.Wait(value);
            if (ret == null)
                return null;
            if (typeHint != null)
            {
                try
                {
                    return JsonSerializer.Deserialize(typeHint, ret);
                }
                catch
                {
                    if (typeHint2 != null)
                    {
                        return JsonSerializer.Deserialize(typeHint2, ret);
                    }
                    else
                        return ret;
                }
            }
            else
                return ret;
        }

        public void Update(string key, object value, string partition = null)
        {
            //mConnection.Wait(mConnection.Strings.TakeLock(0, mName + ":" + key, "LOCK", 2));
            Put(key, value, partition);
            //mConnection.Wait(mConnection.Strings.ReleaseLock(0, mName + ":" + key));
        }

        public void Delete(string key, string partition = null)
        {
            mConnection.Keys.Remove(0, mName + ":" + key);
        }

        public IEnumerable<KeyValuePair<string, object>> BulkGet(List<string> keys, string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            return bulkGet(keys, true, partition, typeHint, typeHint2);
        }

        public IEnumerable<KeyValuePair<string, object>> bulkGet(List<string> keys, bool transformKey, string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            List<KeyValuePair<string, object>> ret = new List<KeyValuePair<string, object>>();
            if (keys.Count == 0)
                return ret;
            string[] queryKeys = keys.ToArray();
            if (transformKey)
            {
                for (int i = 0; i < queryKeys.Length; i++)
                    queryKeys[i] = mName + ":" + queryKeys[i];
            }
            var values = mConnection.Wait(mConnection.Strings.Get(0, queryKeys));
            int offset = mName.Length + 1;
            
            for (int i = 0; i < queryKeys.Length; i++)
            {
                object value = null;
                try
                {
                    value = JsonSerializer.Deserialize(typeHint, values[i]);
                    ret.Add(new KeyValuePair<string, object>(queryKeys[i].Substring(offset), value));
                }
                catch
                {
                    try
                    {
                        value = JsonSerializer.Deserialize(typeHint2, values[i]);
                        ret.Add(new KeyValuePair<string, object>(queryKeys[i].Substring(offset), value));
                    }
                    catch
                    {
                        ret.Add(new KeyValuePair<string, object>(queryKeys[i].Substring(offset), null));
                    }
                }
            }
            return ret;
        }

        public void BulkPut(IEnumerable<KeyValuePair<string, object>> values, string partition = null)
        {
            Dictionary<string, byte[]> insertValues = new Dictionary<string, byte[]>();
            foreach(var value in values)
            {
                if (value.Value is byte[])
                    insertValues.Add(mName + ":"+ value.Key, (byte[])value.Value);
                else
                    insertValues.Add(mName + ":" + value.Key, JsonSerializer.Serialize(value.Value.GetType(), value.Value));
            }
            mConnection.Wait(mConnection.Strings.Set(0, insertValues));
        }

        public void Clear()
        {
            mConnection.Server.FlushDb(0).Wait();
        }
        public Base.DataProviderConfiguration Configuration
        {
            get
            {
                return mConfiguration;
            }
            set
            {
                if (value is RedisConfiguration)
                    mConfiguration = (RedisConfiguration)value;
                else
                    throw new ArgumentException("Configuration is not a ReidsConfiguration type");
                //TODO: react on configuration changes
            }
        }

        public IEnumerable<KeyValuePair<string, object>> GetAll(string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            var keys = mConnection.Wait(mConnection.Keys.Find(0, mName + ":*"));
            return bulkGet(keys.ToList(), false, partition, typeHint, typeHint2);
        }

        public Base.IDataProvider Clone(string newName)
        {
            return new RedisCacheProvider(new RedisConnection(mConnection.Host, mConnection.Port, allowAdmin: true), newName)
            {
                Configuration = new RedisConfiguration()
            };
        }
        protected void Dispose(Boolean disposing)
        {
            if (disposing)
            {
                if (mConnection != null)
                {
                    mConnection.Close(false);
                    mConnection.Dispose();
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
        ~RedisCacheProvider()
        {
            Dispose(false);
        }
    }
}
